# -*- coding: utf-8 -*-
''' Crawler http://sgtt.vn
    @author Hindua88 <dinhmanhdau@gmail.com>
'''
import lxml
import urllib 
import re, sys, os
import mechanize
import datetime, time
import traceback
import workerpool
import hashlib
import urlparse
import binascii
import time
import threading
import pycommonlib  as pyclib
import cStringIO as StringIO
from lxml       import etree
from pymongo    import Connection
from termcolor  import cprint

LOCAL_PATH      = '/home/hoangnamhai/HarvestedData/tintuc/sgtt'
MONGO_SERVER    = 'beta.mana.vn'   
MONGO_PORT      = 27017
TYM_DB          = 'tintuc_v2'
PREFIX          = '/uploads/sgtt' 
SITE_URL        = 'http://sgtt.vn'
MAX_COUNT       = 30
MAX_ARTICLE     = 100
CONNECT         = Connection(MONGO_SERVER, MONGO_PORT)
DB              = CONNECT[TYM_DB]
COLLECTION_ARTICLE  = 'article' 
COLLECTION_CATEGORY = 'category'
os.umask(0000)
logger = pyclib.getMongoLog(MONGO_SERVER, MONGO_PORT, 'sgtt.vn')
totalNewsCrawlered = 0; totalNewsDuplicated = 0
flgCopy             = pyclib.getArgs()
ssh = None; sftp = None
if flgCopy!=None:
    ssh     = pyclib.createSSHClient('mana.vn', 22, 'daudm', '1p%^IRg')
    sftp    = ssh.open_sftp()
    if ssh==None:
        if totalNewsCrawlered == 0 and totalNewsDuplicated == 0:
            logger.critical(unicode("crawler tin tức sgtt.vn không hoạt động", 'utf8'))
            pyclib.forceQuit()
start = 0

listCategory = {
    unicode('Thời sự', 'utf8')      : {'link': 'Thoi-su',  'tag' : 'thoi su', 'flag': False,     'category': unicode('Văn hóa - Xã hội', 'utf-8')},
    unicode('Tiêu dùng', 'utf8')    : {'link': 'Tieu-dung',  'tag' : 'tieu dung', 'flag': False, 'category': unicode('Tiêu dùng', 'utf-8')},
    unicode('Kinh tế', 'utf8')      : {'link': 'Kinh-te',  'tag' : 'kinh te', 'flag': False,     'category': unicode('Kinh doanh', 'utf-8')},
    unicode('Khỏe & vui', 'utf8')   : {'link': 'Khoe-va-vui', 'tag' : 'suc khoe', 'flag': False, 'category': unicode('Sức khỏe - Giới tính', 'utf-8')},
    unicode('Lối sống', 'utf8')     : {'link': 'Loi-song', 'tag' : 'loi song', 'flag': False,    'category': unicode('Văn hóa - Xã hội', 'utf-8')},
    unicode('Văn hóa', 'utf8')      : {'link': 'Van-hoa', 'tag' : 'van hoa', 'flag': False,      'category': unicode('Văn hóa - Xã hội', 'utf-8')},
    unicode('Khoa giáo', 'utf8')    : {'link': 'Khoa-giao', 'tag': 'giao duc', 'flag': False,    'category': unicode('Giáo dục', 'utf-8')},
    unicode('Thể thao', 'utf8')     : {'link': 'The-thao', 'tag' : 'the thao', 'flag': False,   'category': unicode('Thể thao', 'utf-8')},
    unicode('Hương vị quê nhà', 'utf8')     : {'link': 'Huong-vi-que-nha', 'tag': 'am thuc', 'flag': False, 'category': unicode('Ẩm thực', 'utf-8')},
}


stringify = etree.XPath("string()")
def getXMLTree(url, isXML=False, userAgent=False, outputHTML=False):
    try:
        ''' Hàm xây dựng cây:
            - Đọc HTML từ URL đưa vào
            - options : encoding='utf-8' override phương thức encoding
            = isXML : 
                +) False : HTMLParser
                +) True  : XMLParser
        '''
        if type(url).__name__ == 'unicode': url = url.encode('utf-8')
        if userAgent:
            user_agent = 'Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'
            opener = mechanize.build_opener(mechanize.HTTPRefererProcessor)
            opener.addheaders = [("User-agent", user_agent)]
            response = opener.open(url)
        else:
            response  = urllib.urlopen(url)
        html    = response.read()
        if outputHTML: print(html)
        if isXML==False: parser  = etree.HTMLParser(encoding='utf-8')
        else: parser = etree.XMLParser(encoding='utf-8')
        tree    = etree.parse(StringIO.StringIO(html), parser)
        return tree
    except:
        traceback.print_exc()
        return None
    
def getDictionaryCategory(url):
    ''' Hàm lấy các category con của category
        - Ex : Thể thao có 2 category là trong nước và ngoài nước
    '''
    try:
        tree = getXMLTree(url)
        nodeTitle = tree.xpath('//div[@class="barMnuRight"]/ul/li')
        docs= {}
        for node in nodeTitle:
            title_node = node.xpath('./a')
            key = stringify(title_node[0]).strip()
            cat_node   = node.xpath('.//li/a')
            item = []
            for cat in cat_node: item.append(cat.get('href'))
            docs[key] = item
        return docs        
    except:
        traceback.print_exc()
        return None
        
def getDateFromArtilce(t):
    ''' Hàm lấy datetime từ string
        Ex : Ngày 06.07.2011, 10:00 (GMT+7)
        -> datetime.datetime(2011, 07, 06, 10, 00, 0) 
    '''
    try:
        if t==None or t=='': return None
        preg = re.compile(r'(\d+).(\d+).(\d+), (\d+):(\d+)')
        m    = preg.search(t)
        if m!='':
            result = datetime.timedelta(seconds=time.timezone) + datetime.datetime(int(m.group(3)), int(m.group(2)), int(m.group(1)), int(m.group(4)), int(m.group(5)))
            return result
        return datetime.datetime.utcnow()
    except:
        traceback.print_exc()
        return None

def checkArticleDuplicate(link, cat):
    ''' Kiểm tra trùng tin tức          
        - 1 : trùng
        = 0 : 0 bị trùng
    ''' 
    try:
        collection  = DB[COLLECTION_ARTICLE]
        articleId   = hashlib.md5(SITE_URL + str(getArticleId(link))).hexdigest()  
        result = collection.find_one({'hashUrl' : articleId})
        if result!=None:
            cprint('Tin tức đã tồn tại trong cơ sở dữ liệu', 'red'); return 1
        return 0 
    except:
        traceback.print_exc()
        return None

def getArticleId(link):
    ''' Hàm lấy articleId 
        Ex : http://sgtt.vn/Thoi-su/147275/Dieu-gi-lam-nen-chien-thang-cua-tan-Thu-tuong-Thai-Lan.html
        => kết quả 147275
    '''
    try:
        if link==None or link=='': return None 
        arr = link.split('/')
        if len(arr) > 4:
            return arr[-2]
        else: return None
    except:
        traceback.print_exc()
        return None
    
def getCRC32Unsign(text):
    try:
        return str(binascii.crc32(text) & 0xffffffffL)
    except:
        traceback.print_exc()
        return None
            
def processArticle(link, cat, root_id, lft, count=0, loop=0):
    ''' Hàm xử lý một tin tức
        - Kiểm tra xem tin tức đã tồn tại hay chưa (1 đã tồn tại, 0 chưa tồn tại)
        - Lấy tiêu đề, ảnh (có thể không có)
    '''
    try:
        global totalNewsCrawlered, totalNewsDuplicated
        check = checkArticleDuplicate(link, cat)
        if check==1: totalNewsDuplicated += 1; return 1, 1
        if check==None: return 0, 1
        tree = getXMLTree(link)
        print pyclib.toAscii('Save article  : ' + link)
        # Lấy ngày tháng
        listNode = tree.xpath('//div[@class="boxTitleNewsDetail"]/div[2]')
        if len(listNode) > 0:
            sgtt_date = getDateFromArtilce(stringify(listNode[0]).strip())
            if sgtt_date==None: return None, None
        # Lấy tiêu đề, Nội dung
        listNode = tree.xpath('//div[@class="boxContentNewsDetail"]/div/*')
        if len(listNode) <= 0: return None, None
        data = []; title = ''; tcount = 0; thumbnail =''; intro = ''; authorArticle = '' 
        for node in listNode:
            if node.tag in ['style', 'script']: continue
            if node.tag=='table':
                imgNode = node.xpath('.//img')
                if len(imgNode) > 0:
                    if len(imgNode) > 1:
                        cprint('Count img : ' + str(len(imgNode)), 'red')
                        listChildNode = node.xpath('.//tr')
                        if len(listChildNode) < 0: return None
                        for cnode in listChildNode:
                            listImg = cnode.xpath('.//img')
                            ccontent =  stringify(cnode).strip()
                            if len(listImg) > 0:
                                for img in listImg:
                                    src = img.get('src')
                                    if src==None or src=='':
                                        src = img.get('originalpath')
                                    src = urlparse.urljoin(SITE_URL, src)
                                    linkImage = src
                                    result = None; source = file_name = ''; size = 0
                                    if flgCopy!=None:
                                        result, source, file_name, size = pyclib.saveImageWithSCP(linkImage, PREFIX, LOCAL_PATH, ssh, sftp)
                                    else:
                                        result, source, file_name, size = pyclib.saveImage(linkImage, PREFIX, LOCAL_PATH)
                                    if result!=None:
                                        data.append({'data': source, 'type' : 'image', 'caption' : ''})    
                            if ccontent!=None and ccontent!='':
                                ccontent = ' '.join(ccontent.split())
                                print pyclib.toAscii(ccontent)
                                data.append({'data': ccontent, 'type': 'text'})
                    else:
                        src = imgNode[0].get('originalpath')
                        if src==None or src=='':
                            src = imgNode[0].get('src')
                        src = '{0}/{1}'.format(SITE_URL, src)          
                        linkImage = src
                        result = None; source = file_name = ''; size = 0
                        if flgCopy!=None:
                            result, source, file_name, size = pyclib.saveImageWithSCP(linkImage, PREFIX, LOCAL_PATH, ssh, sftp)
                        else:
                            result, source, file_name, size = pyclib.saveImage(linkImage, PREFIX, LOCAL_PATH)
                        if result!=None and tcount==0:
                            tcount = 1; thumbnail = source   
                        nodeCaption = node.xpath('.//span[@class="pCaption"]')
                        if len(nodeCaption) > 0: caption = stringify(nodeCaption[0]).strip()
                        else: caption = ''
                        nodeAuthor = node.xpath('.//span[@class="pImgAuthor"]')
                        if len(nodeAuthor) > 0: caption += stringify(nodeAuthor[0]).strip()
                        data.append({'data': source, 'type' : 'image', 'caption' : caption})      
            else:
                text = stringify(node).strip()
                if text!=None and text!='':
                    if node.get('class')=='pSurTitle': continue
                    elif node.get('class')=='pTitle': title = text
                    elif node.get('class')=='pHead': intro = text
                    elif node.get('class')=='pAuthor': authorArticle = text; continue
                    elif node.get('class')=='pInterTitle':
                        print pyclib.toAscii(text)
                        data.append({'data': text, 'type' : 'textbold'})
                    else: 
                        text = ' '.join(text.split())
                        print pyclib.toAscii(text)
                        data.append({'data': text, 'type' : 'text'})
            if node.tail and node.tail!='':
                text = ' '.join(node.tail.split())
                text = text.strip()
                if text!='': data.append({'data': node.tail, 'type' : 'text'})
                
        articleId = hashlib.md5(SITE_URL + str(getArticleId(link))).hexdigest()
        collection = DB[COLLECTION_ARTICLE]
        
        tags = [];  
        cdata = unicode('Khoa giáo', 'utf-8')
        if cat==cdata:
            preg = re.compile(r'/Khoa-hoc-va-cong-nghe/')
            if preg.search(link): tags.append('khcn')
            else:
                preg = re.compile(r'/Tin-hoc/')
                if preg.search(link): tags.append('tin hoc')
                else: tags.append('giao duc')
        else: 
            tag = listCategory[cat]['tag']
            tags.append(tag)
        title = pyclib.toUnicodeDungSan(title)
        doc = {
            'hashUrl': articleId,
            'title': title,
            'root': root_id,
            'category': lft,
            'thumbnail': thumbnail,
            'description': intro,
            'content': data,
            'newsLink': link,
            'timestamp': time.time(),
            'lastupdate': datetime.datetime.utcnow(),
            'date': datetime.datetime.utcnow(),
            'update': sgtt_date,
            'author' : authorArticle,
            'is_active': True,
            'source': 'sgtt.vn',
            'tags': tags
        }
        cprint ('hashUrl: ' + articleId, 'green')
        print pyclib.toAscii('title : ' + title)
        cprint ('thumbnail : ' + thumbnail, 'green')
        print pyclib.toAscii('description : ' + intro)
        print pyclib.toAscii('newsLink : ' + link)
        print pyclib.toAscii('author : ' + authorArticle)
        print 'Date : ', sgtt_date
        if len(data) > 0:
            totalNewsCrawlered += 1
            collection.save(doc)
            return 0, 1    
    except:
        traceback.print_exc()
        return None, None

def getCategoryWithName(name):
    try:
        collection = DB[COLLECTION_CATEGORY]
        result = collection.find_one({'data': name}, {'root_id': 1, 'lft': 1})    
        if result!=None:
            return result['root_id'], result['lft']
        else:
            cprint('Category chưa tồn tại trong sơ sở dữ liệu', 'red')
    except:
        traceback.print_exc()

def processCategory(link, cat, root_id, lft, count = 0, loop = 0):
    ''' Hàm xử lý category
        - Lấy về tất cả các tiêu đề 
    '''
    try:
        if listCategory[cat]['flag']: return
        # Nếu là category khoa giáo thì gán lại root_id, lft 
        root_id, lft = checkCategoryExists(cat)
        data = unicode('Khoa giáo', 'utf-8')
        if cat==data:
            preg = re.compile(r'/Khoa-hoc-va-cong-nghe/')
            if preg.search(link):
                category = unicode('Công nghệ số', 'utf-8')
                root_id, lft = getCategoryWithName(category)
            else:
                preg = re.compile(r'/Tin-hoc/')
                if preg.search(link):
                    category = unicode('Công nghệ số', 'utf-8')
                    root_id, lft = getCategoryWithName(category)
        if count > MAX_COUNT or loop > MAX_ARTICLE: 
            cprint('Thoát khỏi chương trình do trùng lặp vượt quá giới hạn của tin tức hoặc quá số tin cần lấy', 'red')
            listCategory[cat]['flag'] = True; return 
        print 'Count : ', count
        print 'Loop : ', loop 
        print pyclib.toAscii('process : ' + link)
        tree = getXMLTree(link)
        # lấy tiêu đề tin tức chỉ có ở trang đầu tiên
        first_title = tree.xpath('//div[@class="ktds_listMnuNews"]/div/div/a')
        if len(first_title) > 0:
            href  = first_title[0].get('href')
            if re.search(listCategory[cat]['link'], href):
                c, l = processArticle(href, cat, root_id, lft, count, loop)
                if c!=None: count += c; loop += l
                else: 
                    retry = 0
                    while(retry < 3):
                        c, l = processArticle(href, cat, root_id, lft, count, loop)
                        if c!=None: count += c; loop += l; break
                        retry += 1
        # Lấy các tiêu đề và link tiếp theo
        list_title = tree.xpath('//div[@id="boxCOntentListNews"]/div/div/a')
        if len(list_title) > 0:
            for node in list_title:
                if re.search('sgtt.vn', node.get('href')):
                    c, l = processArticle(node.get('href'), cat, root_id, lft, count, loop)
                    if c!=None: count += c; loop += l
                else: 
                    retry = 0
                    while(retry < 3):
                        c, l = processArticle(node.get('href'), cat, root_id, lft, count, loop)
                        if c!=None: count += c; loop += l; break
                        retry += 1
        else: return
        # Lấy trang tiếp theo
        pageNode = tree.xpath('//div[@class="boxToolsEdit"]/div/a')
        if len(pageNode) > 0:
            link = urlparse.urljoin(link, pageNode[0].get('href').strip())
            processCategory(link, cat, root_id, lft, count, loop)
        else: return 
    except:
        traceback.print_exc()
        
def checkCategoryExists(cat):
    ''' Kiểm tra xem category đã tồn tại hay chưa
        - Nếu chưa tồn tại thì không làm gì cả
        - Lấy root_id và lft của category
    '''                  
    try:
        data = listCategory[cat]['category']
        print 'Category : ', pyclib.toAscii(data)
        categoryCollection = DB[COLLECTION_CATEGORY]
        result = categoryCollection.find_one({'data': data})
        if result==None: 
            cprint('Chưa tồn tại category', 'red')
            return None, None
        else: return result['root_id'], result['lft']
    except:
        traceback.print_exc()
        return None, None
    
def processAll(cat):
    ''' Xử lý từng category có trong categoryList
    '''
    try:
        dict = getDictionaryCategory(SITE_URL)
        listCat = dict[cat]
        for link in listCat: 
            print pyclib.toAscii('########## Process category ' + cat)
            root_id, lft = checkCategoryExists(cat)
            cprint('Root :' + str(root_id), 'yellow')
            cprint('Lft :' + str(lft), 'yellow')
            if root_id==None or lft==None: return
            processCategory(link, cat, root_id, lft)        
    except:
        traceback.print_exc()

def timeOut():        
    global totalNewsCrawlered, totalNewsDuplicated
    while True:
        delta = time.time() - start
        if delta > 900:
            print 'Dừng chương trình vì vượt quá thời gian chạy.', datetime.datetime.now()
            if totalNewsCrawlered == 0 and totalNewsDuplicated == 0:
                logger.critical(unicode("crawler tin tức sgtt.vn không hoạt động", 'utf8'))
            else:
                logger.info(unicode("Tổng số bài viết crawled: {0}, tổng số bài trùng lặp {1}".format(totalNewsCrawlered, totalNewsDuplicated), 'utf8'))
            pid = os.getpid(); os._exit(1); os.kill(pid, 9)
        time.sleep(30)

if __name__ == '__main__':
    try:
        start = time.time() 
        logger.info('start crawler sgtt.vn')
        totalNewsCrawlered = 0; totalNewsDuplicated = 0
        timeout = threading.Thread(target=timeOut).start()
        pool = workerpool.WorkerPool(size=1)
        pool.map(processAll, listCategory.keys())
        pool.shutdown(); pool.wait()
        if flgCopy!=None: ssh.close()
        if totalNewsCrawlered == 0 and totalNewsDuplicated == 0:
            logger.critical(unicode("crawler tin tức sgtt.vn không hoạt động", 'utf8'))
        else:
            logger.info(unicode("Tổng số bài viết crawled: {0}, tổng số bài trùng lặp {1}".format(totalNewsCrawlered, totalNewsDuplicated), 'utf8'))
        print 'Finished.', datetime.datetime.now()
        logger.info('finished crawler sgtt.vn')
        forceQuit()
    except:
        traceback.print_exc()

